package com.gitee.loyo.utils;

import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.Json;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import static java.nio.charset.StandardCharsets.UTF_8;

public class HttpUtils {
    private final static byte PACK_TYPE_END = 0x0;
    private final static byte PACK_TYPE_REQUEST_LINE = 0x1;
    private final static byte PACK_TYPE_HEADER = 0x2;
    private final static byte PACK_TYPE_CHUNK = 0x3;
    private final static byte PACK_TYPE_CODE = 0x4;
    private final static byte PACK_TYPE_CLOSE = 0xf;

    public static String splitPort(String host){
        int i = host.indexOf(':');
        if(i < 0){
            return host;
        }else{
            return host.substring(0, i);
        }
    }

    public static Buffer wrapReqLine(String requestId, HttpMethod method, String uri) {
        byte[] requestIdBytes = requestId.getBytes(UTF_8);
        byte[] methodBytes = method.name().getBytes(UTF_8);
        byte[] uriBytes = uri.getBytes(UTF_8);
        return Buffer.buffer()
            .appendByte(PACK_TYPE_REQUEST_LINE)
            .appendInt(requestIdBytes.length)
            .appendBytes(requestIdBytes)
            .appendInt(methodBytes.length)
            .appendBytes(methodBytes)
            .appendInt(uriBytes.length)
            .appendBytes(uriBytes)
            ;
    }

    public static Buffer wrapHeader(String requestId, MultiMap headers) {
        byte[] requestIdBytes = requestId.getBytes(UTF_8);
        Buffer buffer = Buffer.buffer()
            .appendByte(PACK_TYPE_HEADER)
            .appendInt(requestIdBytes.length)
            .appendBytes(requestIdBytes)
            .appendInt(headers.size());
        for (Map.Entry<String, String> header : headers) {
            byte[] keyBytes = header.getKey().getBytes(UTF_8);
            byte[] valueBytes = header.getValue().getBytes(UTF_8);
            buffer.appendInt(keyBytes.length).appendBytes(keyBytes)
                .appendInt(valueBytes.length).appendBytes(valueBytes);
        }
        return buffer;
    }

    public static Buffer wrapChunk(String requestId, Buffer data) {
        byte[] requestIdBytes = requestId.getBytes(UTF_8);
        return Buffer.buffer()
            .appendByte(PACK_TYPE_CHUNK)
            .appendInt(requestIdBytes.length)
            .appendBytes(requestIdBytes)
            .appendInt(data.length())
            .appendBuffer(data);
    }

    public static Buffer wrapCode(String requestId, int code, String message){
        byte[] requestIdBytes = requestId.getBytes(UTF_8);
        byte[] messageBytes = message.getBytes(UTF_8);
        return Buffer.buffer()
            .appendByte(PACK_TYPE_CODE)
            .appendInt(requestIdBytes.length)
            .appendBytes(requestIdBytes)
            .appendInt(code)
            .appendInt(messageBytes.length)
            .appendBytes(messageBytes);
    }

    public static Buffer wrapEnd(String requestId) {
        byte[] requestIdBytes = requestId.getBytes(UTF_8);
        return Buffer.buffer()
            .appendByte(PACK_TYPE_END)
            .appendInt(requestIdBytes.length)
            .appendBytes(requestIdBytes);
    }

    public static Buffer wrapClose(String requestId) {
        byte[] requestIdBytes = requestId.getBytes(UTF_8);
        return Buffer.buffer()
            .appendByte(PACK_TYPE_CLOSE)
            .appendInt(requestIdBytes.length)
            .appendBytes(requestIdBytes);
    }

    public static String unwrapRequestId(Buffer data){
        return new String(data.getBytes(Byte.BYTES + Integer.BYTES, Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES)), UTF_8);
    }

    public static void unwrapRequestPack(Buffer data, Handler<HttpMethod> httpMethodHandler, Handler<String> uriHandler, Handler<Map.Entry<String, String>> headerHandler,
                                         Handler<byte[]> chunkHandler, Handler<Void> endHandler, Handler<Void> closeHandler) {
        int index;
        int length;
        switch (data.getByte(0)){
            case PACK_TYPE_REQUEST_LINE: {
                index = Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES);
                length = data.getInt(index);
                index += Integer.BYTES;

                HttpMethod httpMethod = HttpMethod.valueOf(new String(data.getBytes(index, index + length), UTF_8));
                httpMethodHandler.handle(httpMethod);
                index += length;

                length = data.getInt(index);
                index += Integer.BYTES;
                String uri = new String(data.getBytes(index, index + length), UTF_8);
                uriHandler.handle(uri);
                return;
            }
            case PACK_TYPE_HEADER: {
                index = Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES);
                int size = data.getInt(index);
                index += Integer.BYTES;

                for (int i = 0; i < size; i++) {
                    length = data.getInt(index);
                    index += Integer.BYTES;
                    String key = new String(data.getBytes(index, index + length), UTF_8);
                    index += length;

                    length = data.getInt(index);
                    index += Integer.BYTES;
                    String value = new String(data.getBytes(index, index + length), UTF_8);
                    index += length;

                    headerHandler.handle(new Map.Entry<String, String>() {
                        @Override
                        public String getKey() {
                            return key;
                        }

                        @Override
                        public String getValue() {
                            return value;
                        }

                        @Override
                        public String setValue(String value) {
                            throw new UnsupportedOperationException();
                        }
                    });
                }
                return;
            }
            case PACK_TYPE_CHUNK: {
                index = Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES);
                length = data.getInt(index);
                index += Integer.BYTES;
                chunkHandler.handle(data.getBytes(index, index + length));
                return;
            }
            case PACK_TYPE_END: {
                endHandler.handle(null);
                return;
            }
            case PACK_TYPE_CLOSE: {
                closeHandler.handle(null);
                return;
            }
        }
    }

    public static void unwrapResponsePack(Buffer data, Handler<Integer> codeHandler, Handler<String> messageHandler, Handler<Map.Entry<String, String>> headerHandler,
                                          Handler<byte[]> chunkHandler, Handler<Void> endHandler) {
        int index;
        int length;
        switch (data.getByte(0)){
            case PACK_TYPE_CODE: {
                index = Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES);
                int code = data.getInt(index);
                codeHandler.handle(code);
                index += Integer.BYTES;

                length = data.getInt(index);
                index += Integer.BYTES;
                String message = new String(data.getBytes(index, index + length), UTF_8);
                messageHandler.handle(message);
                return;
            }
            case PACK_TYPE_HEADER: {
                index = Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES);
                int size = data.getInt(index);
                index += Integer.BYTES;

                for (int i = 0; i < size; i++) {
                    length = data.getInt(index);
                    index += Integer.BYTES;
                    String key = new String(data.getBytes(index, index + length), UTF_8);
                    index += length;

                    length = data.getInt(index);
                    index += Integer.BYTES;
                    String value = new String(data.getBytes(index, index + length), UTF_8);
                    index += length;

                    headerHandler.handle(new Map.Entry<String, String>() {
                        @Override
                        public String getKey() {
                            return key;
                        }

                        @Override
                        public String getValue() {
                            return value;
                        }

                        @Override
                        public String setValue(String value) {
                            throw new UnsupportedOperationException();
                        }
                    });
                }
                return;
            }
            case PACK_TYPE_CHUNK: {
                index = Byte.BYTES + Integer.BYTES + data.getInt(Byte.BYTES);
                length = data.getInt(index);
                index += Integer.BYTES;
                chunkHandler.handle(data.getBytes(index, index + length));
                return;
            }
            case PACK_TYPE_END: {
                endHandler.handle(null);
                return;
            }
        }
    }

    public static <T> void getBodyAsJson(HttpServerRequest request, Class<T> type, Promise<T> p) {
        Buffer buffer = Buffer.buffer();
        try{
            request.endHandler(event ->
                p.complete(buffer.length() == 0 ? null : Json.decodeValue(buffer, type))
            ).exceptionHandler(p::fail).handler(data -> {
                buffer.appendBuffer(data);
            });
        } catch (Exception e){
            p.fail(e);
        }
    }

    public static void getBodyAsString(HttpServerRequest request, Promise<String> p) {
        Buffer buffer = Buffer.buffer();
        try{
            request.endHandler(event ->
                p.complete(buffer.length() == 0 ? "" : buffer.toString(UTF_8))
            ).exceptionHandler(p::fail).handler(data -> {
                buffer.appendBuffer(data);
            });
        } catch (Exception e){
            p.fail(e);
        }
    }

    public static String decodeGZip(byte[] data){
        byte[] buffer = new byte[1024];
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ByteArrayInputStream bis = new ByteArrayInputStream(data);
             GZIPInputStream gzipInputStream = new GZIPInputStream(bis)) {

            int bytes_read;

            while ((bytes_read = gzipInputStream.read(buffer)) > 0) {
                baos.write(buffer, 0, bytes_read);
            }
            return new String(baos.toByteArray(), UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            return new String(data, UTF_8);
        }
    }
}
